/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "crc.h"
#include "dma.h"
#include "sai.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdarg.h>
#include "edge-impulse-sdk/classifier/ei_run_classifier.h"
#include "../../Drivers/BSP/STM32746G-Discovery\stm32746g_discovery_audio.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
typedef struct {
    int16_t *buffers[2];
    uint8_t buf_select;
    volatile uint8_t buf_ready;
    uint32_t buf_count;
    uint32_t n_samples;
} inference_t;
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define I2S_BUF_LEN 6400  // 4x desired size to downsample
#define I2S_BUF_SKIP 2    // (2x L/R ch) * (1x sample rate)
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
extern UART_HandleTypeDef huart1;
// Settings
static bool debug_nn = false; // Set this to true to see e.g. features generated from the raw signal
static bool record_ready = false;

// Globals
uint32_t i2s_buf[I2S_BUF_LEN];
static inference_t inference;
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void PeriphCommonClock_Config(void);

/* USER CODE BEGIN PFP */
static int get_audio_signal_data(size_t offset, size_t length, float *out_ptr);
static void audio_buffer_inference_callback(uint32_t n_bytes, uint32_t offset);
bool ei_microphone_inference_record(void);
bool ei_microphone_inference_end(void);
void ei_printf(const char *format, ...);
void vprint(const char *fmt, va_list argp);
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */
	bool m;
	signal_t signal;
	ei_impulse_result_t result = { 0 };
	int print_results = -(EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW);
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* Configure the peripherals common clocks */
  PeriphCommonClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_CRC_Init();
  MX_SAI2_Init();
  MX_USART1_UART_Init();
	/* USER CODE BEGIN 2 */
	// Create double buffer
	inference.buffers[0] = (int16_t*) malloc(
			EI_CLASSIFIER_SLICE_SIZE * sizeof(int16_t));
	if (inference.buffers[0] == NULL) {
		ei_printf("ERROR: Could not create buffer 1. Likely ran out of heap memory.\r\n");
	}
	inference.buffers[1] = (int16_t*) malloc(
			EI_CLASSIFIER_SLICE_SIZE * sizeof(int16_t));
	if (inference.buffers[1] == NULL) {
		ei_printf("ERROR: Could not create buffer 2. Likely ran out of heap memory.\r\n");
	}

	// Set inference parameters
	inference.buf_select = 0;
	inference.buf_count = 0;
	inference.n_samples = EI_CLASSIFIER_SLICE_SIZE;
	inference.buf_ready = 0;
	// Set signal parameters
	signal.total_length = EI_CLASSIFIER_SLICE_SIZE;
	signal.get_data = &get_audio_signal_data;

	//16K采样频率，16bit量化，双声道
	if (BSP_AUDIO_IN_Init(SAI_AUDIO_FREQUENCY_16K,DEFAULT_AUDIO_IN_BIT_RESOLUTION, 2) == AUDIO_OK)
		ei_printf("BSP_AUDIO_IN_Init OK!\n\r");
	else
		ei_printf("BSP_AUDIO_IN_Init ERROR!\n\r");
	//注意采样buffer的单位及长度
	if (BSP_AUDIO_IN_Record((uint16_t*) i2s_buf, I2S_BUF_LEN) == AUDIO_OK)
		ei_printf("BSP_AUDIO_IN_Record OK!\n\r");
	else
		ei_printf("BSP_AUDIO_IN_Record ERROR!\n\r");

	// Start doing inference
	record_ready = true;
	/* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
	while (1) {
		// Wait until buffer is full
		m = ei_microphone_inference_record();
		if (!m) {
			ei_printf("ERROR: Audio buffer overrun\r\n"); //推理时间太长造成
			break;
		}
		// Do classification (i.e. the inference part)
		result = { 0 };
		EI_IMPULSE_ERROR r = run_classifier_continuous(&signal, &result, debug_nn);
		if (r != EI_IMPULSE_OK) {
			ei_printf("ERROR: Failed to run classifier (%d)\r\n", r);
			break;
		}
		// Print output predictions (once every 4 predictions)
		if (++print_results >= (EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW >> 1)) {
			ei_printf("Predictions (DSP: %d ms, NN: %d ms)\r\n",
					result.timing.dsp, result.timing.classification);
			for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
				ei_printf("    %s:", result.classification[ix].label);
				ei_printf_float(result.classification[ix].value);
				ei_printf("\r\n");
			}
			print_results = 0;

			//print label if it'value is above 0.5 threshold
			for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
				if (result.classification[ix].value > 0.5) {
					ei_printf("The inference result is %s\r\n",
							result.classification[ix].label);
				}
			}
		}
		/* USER CODE END WHILE */
		/* USER CODE BEGIN 3 */
	}
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure LSE Drive Capability
  */
  HAL_PWR_EnableBkUpAccess();

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 25;
  RCC_OscInitStruct.PLL.PLLN = 432;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Activate the Over-Drive mode
  */
  if (HAL_PWREx_EnableOverDrive() != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_7) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief Peripherals Common Clock Configuration
  * @retval None
  */
void PeriphCommonClock_Config(void)
{
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};

  /** Initializes the peripherals clock
  */
  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SAI2;
  PeriphClkInitStruct.PLLSAI.PLLSAIN = 192;
  PeriphClkInitStruct.PLLSAI.PLLSAIR = 2;
  PeriphClkInitStruct.PLLSAI.PLLSAIQ = 2;
  PeriphClkInitStruct.PLLSAI.PLLSAIP = RCC_PLLSAIP_DIV2;
  PeriphClkInitStruct.PLLSAIDivQ = 1;
  PeriphClkInitStruct.PLLSAIDivR = RCC_PLLSAIDIVR_2;
  PeriphClkInitStruct.Sai2ClockSelection = RCC_SAI2CLKSOURCE_PLLSAI;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */
bool ei_microphone_inference_record(void) {
	bool ret = true;
	// Check to see if the buffer has overrun
	if (inference.buf_ready == 1) {
		ret = false;
	}
	// %%%TODO: make this non-blocking
	while (inference.buf_ready == 0) {
		continue;
	}
	inference.buf_ready = 0;
	return ret;
}

/**
 * @brief      Stop audio sampling, release sampling buffers
 *
 * @return     false on error
 */
bool ei_microphone_inference_end(void) {
	// Stop I2S
	HAL_SAI_DMAStop(&hsai_BlockA2);

	// Free up double buffer
	record_ready = false;
	free(inference.buffers[0]);
	free(inference.buffers[1]);

	return true;
}

/**
 * Called when the first half of the receive buffer is full
 */
void HAL_SAI_RxHalfCpltCallback(SAI_HandleTypeDef *hsai) {
	if (record_ready == true) {
		audio_buffer_inference_callback(I2S_BUF_LEN / I2S_BUF_SKIP, 0);
	}
}

/**
 * Called when the second half of the receive buffer is full
 */
void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai) {
	if (record_ready == true) {
		audio_buffer_inference_callback(I2S_BUF_LEN / I2S_BUF_SKIP,
				I2S_BUF_LEN >> 1);
	}
}

/**
 * @brief      Copy sample data in selected buf and signal ready when buffer is full
 *
 * @param[in]  n_bytes  Number of bytes to copy
 * @param[in]  offset   offset in sampleBuffer
 */
static void audio_buffer_inference_callback(uint32_t n_bytes, uint32_t offset) {
	// Copy samples from I2S buffer to inference buffer. 16-bit, 16kHz
	for (uint32_t i = 0; i < (n_bytes >> 1); i++) { //处理半个buffer的次数
		inference.buffers[inference.buf_select][inference.buf_count++] =
				(int16_t) (i2s_buf[offset + (I2S_BUF_SKIP * i)]);

		if (inference.buf_count >= inference.n_samples) {
			inference.buf_select ^= 1;
			inference.buf_count = 0;
			inference.buf_ready = 1;
		}
	}
}

/**
 * Get raw audio signal data
 */
static int get_audio_signal_data(size_t offset, size_t length, float *out_ptr) {
	numpy::int16_to_float(&inference.buffers[inference.buf_select ^ 1][offset],
			out_ptr, length);
	return 0;
}

/**
 * Low-level print function that uses UART to print status messages.
 */
void vprint(const char *fmt, va_list argp) {
	char string[200];
	if (0 < vsprintf(string, fmt, argp)) // build string
	{
		HAL_UART_Transmit(&huart1, (uint8_t*) string, strlen(string), 0xffffff);
	}
}

/**
 * Wrapper for vprint. Use this like you would printf to print messages to the serial console.
 */
void ei_printf(const char *format, ...) {
	va_list myargs;
	va_start(myargs, format);
	vprint(format, myargs);
	va_end(myargs);
}
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
